﻿using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger;
using Weazel.Badger.Vhdl;
using Weazel.Badger.Vhdl.Types;
using Weazel.Badger.Vhdl.Statements;
using Weazel.Badger.Vhdl.Expressions;

namespace Weazel.Willows.InterfaceGeneration
{
	public class HardwareQueueEntity
		: RegisterEntity
	{
		private int dataWidth;
		private int length;

		public static string DataInPortName { get { return "data_in"; } }
		public static string CanWritePortName { get { return "can_write"; } }
		public static string DoWritePortName { get { return "do_write"; } }

		public static string DataOutPortName { get { return "data_out"; } }
		public static string CanReadPortName { get { return "can_read"; } }
		public static string DoReadPortName { get { return "do_read"; } }

		public static string ClockPortName { get { return "clock"; } }
		public static string ResetPortName { get { return "reset"; } }

		public static string NoLongerFullPortName { get { return "no_longer_full"; } }
		public static string NoLongerEmptyPortName { get { return "no_longer_empty"; } }

		private VhdlRegister lastCanReadValueRegister;
		private VhdlRegister lastCanWriteValueRegister;

		// Ports
		private Port dataInPort;
		private Port canWritePort;
		private Port doWritePort;

		private Port dataOutPort;
		private Port canReadPort;
		private Port doReadPort;

		private Port noLongerFullPort;
		private Port noLongerEmptyPort;

		private void createPorts()
		{
			dataInPort = new Port(new StdLogicVector(dataWidth), null, DataInPortName, PortDirection.In, 0);
			AddPort(dataInPort);

			canWritePort = new Port(new StdLogic(), null, CanWritePortName, PortDirection.Out, 1);
			AddPort(canWritePort);

			doWritePort = new Port(new StdLogic(), null, DoWritePortName, PortDirection.In, 2);
			AddPort(doWritePort);

			dataOutPort = new Port(new StdLogicVector(dataWidth), null, DataOutPortName, PortDirection.Out, 3);
			AddPort(dataOutPort);

			canReadPort = new Port(new StdLogic(), null, CanReadPortName, PortDirection.Out, 4);
			AddPort(canReadPort);

			doReadPort = new Port(new StdLogic(), null, DoReadPortName, PortDirection.In, 5);
			AddPort(doReadPort);

			noLongerEmptyPort = new Port(new StdLogic(), null, NoLongerEmptyPortName, PortDirection.Out, 6);
			AddPort(noLongerEmptyPort);

			noLongerFullPort = new Port(new StdLogic(), null, NoLongerFullPortName, PortDirection.Out, 7);
			AddPort(noLongerFullPort);

			ClockPort = new Port(new StdLogic(), null, ClockPortName, PortDirection.In, 8);
			AddPort(ClockPort);

			ResetPort = new Port(new StdLogic(), null, ResetPortName, PortDirection.In, 9);
			AddPort(ResetPort);
		}

		private void insertNoLongerEmptyCircuitry(Signal canReadSignal)
		{
			// connect the 'can_read' signal of the reading end of the 
			// fifo's with the 'can_read' port of this entity
			SignalAssignmentStatement assignPort =
				new SignalAssignmentStatement(
					canReadPort,
					new SignalReferenceExpression(
						Statements,
						canReadSignal
					)
				);

			Statements.Add(assignPort);

			// connect the input of the 'last can read value' register	
			SignalAssignmentStatement assignRegister =
				new SignalAssignmentStatement(
					lastCanReadValueRegister.WriteSignal,
					new SignalReferenceExpression(
						Statements,
						canReadSignal
					)
				);

			Statements.Add(assignRegister);

			// no_longer_empty <= can_read and not last_can_read_value..
			UnaryExpression notLastCanReadValue =
				new UnaryExpression(
					Statements,
					Weazel.Badger.Vhdl.Operators.UnaryOperators.Not,
					new SignalReferenceExpression(
						Statements,
						lastCanReadValueRegister.ReadSignal
					),
					new StdLogic(),
					null
				);

			BinaryExpression notLastCanReadValueAndCanWrite =
				new BinaryExpression(
					Statements,
					Weazel.Badger.Vhdl.Operators.BinaryOperators.And,
					new SignalReferenceExpression(
						Statements,
						canReadSignal
					),
					notLastCanReadValue,
					new StdLogic(),
					null
				);

			SignalAssignmentStatement assignNoLongerEmptyPort =
				new SignalAssignmentStatement(
					noLongerEmptyPort,
					notLastCanReadValueAndCanWrite
				);

			Statements.Add(assignNoLongerEmptyPort);
		}

		private void insertNoLongerFullCircuitry(Signal canWriteSignal)
		{
			// connect the 'can_write' signal of the writing end of the 
			// fifo's with the 'can_write' port of this entity
			SignalAssignmentStatement assignPort = 
				new SignalAssignmentStatement(
					canWritePort, 
					new SignalReferenceExpression(
						Statements, 
						canWriteSignal
					)
				);

			Statements.Add(assignPort);

			// connect the input of the 'last can write value' register	
			SignalAssignmentStatement assignRegister = 
				new SignalAssignmentStatement(
					lastCanWriteValueRegister.WriteSignal,
					new SignalReferenceExpression(
						Statements,
						canWriteSignal
					)
				);

			Statements.Add(assignRegister);

			// no_longer_full <= can_write and not last_can_write_value..
			UnaryExpression notLastCanWriteValue =
				new UnaryExpression(
					Statements,
					Weazel.Badger.Vhdl.Operators.UnaryOperators.Not,
					new SignalReferenceExpression(
						Statements,
						lastCanWriteValueRegister.ReadSignal
					),
					new StdLogic(),
					null
				);

			BinaryExpression notLastCanWriteValueAndCanWrite =
				new BinaryExpression(
					Statements,
					Weazel.Badger.Vhdl.Operators.BinaryOperators.And,
					new SignalReferenceExpression(
						Statements,
						canWriteSignal
					),
					notLastCanWriteValue,
					new StdLogic(),
					null
				);

			SignalAssignmentStatement assignNoLongerFullPort =
				new SignalAssignmentStatement(
					noLongerFullPort,
					notLastCanWriteValueAndCanWrite
				);

			Statements.Add(assignNoLongerFullPort);
		}

		public HardwareQueueEntity(int bitwidth, int length, FifoElementRegistry elementRegistry)
			: base(String.Format("hw_queue_w{0}_l{1}", bitwidth, length))
		{
			this.dataWidth = bitwidth;
			this.length = length;

			createPorts();

			ComponentDeclaration component =
				elementRegistry.GetFifoElementComponentDeclaration(bitwidth);

			AddDeclarativeItem(component);

			lastCanWriteValueRegister = 
				CreateRegister("last_can_write_value", new StdLogic());

			// Create fifo element instances and connect them.
			// The datawires are connected with each other. The can/do read/write ports
			// are crossed.


			Signal canReadToDoReadSignal = new Signal(new StdLogic(), null);
			AddDeclarativeItem(canReadToDoReadSignal);
			AddSignal(canReadToDoReadSignal);

			Console.WriteLine(" ***** " + canReadToDoReadSignal.Name);

			Signal doReadToCanWriteSignal = doWritePort;
			Signal dataSignal = dataInPort;

			insertNoLongerFullCircuitry(canReadToDoReadSignal);

			for (int i = 0; i < length; i++)
			{
				PortMap portMap = new PortMap(string.Format("element_{0}",i), component.Name);

				portMap.Add(FifoElementEntity.DataInPortName, dataSignal.Name);
				portMap.Add(FifoElementEntity.CanWritePortName, canReadToDoReadSignal.Name);
				portMap.Add(FifoElementEntity.DoWritePortName, doReadToCanWriteSignal.Name);

				if (i == length - 1)
				{
					// the last element will become the "reading end"
					// connect the last element to the ports of this entity
					dataSignal = dataOutPort;
					canReadToDoReadSignal = doReadPort; 

					// the 'can_read' signal cannot be assigned directly to the
					// 'can_read' port of this entity since the value it needed
					// in the 'no longer empty' calculation. Create a new signal
					// which will be connected with the 'can_read' port later by
					// the 'insertNoLongerEmptyCircuitry' method.
					doReadToCanWriteSignal = new Signal(new StdLogic(), null);
					AddSignal(doReadToCanWriteSignal);
					AddDeclarativeItem(doReadToCanWriteSignal);
				}
				else
				{
					dataSignal = new Signal(new StdLogicVector(bitwidth), null);
					AddSignal(dataSignal);
					AddDeclarativeItem(dataSignal);

					doReadToCanWriteSignal = new Signal(new StdLogic(), null);
					AddSignal(doReadToCanWriteSignal);
					AddDeclarativeItem(doReadToCanWriteSignal);

					canReadToDoReadSignal = new Signal(new StdLogic(), null);
					AddSignal(canReadToDoReadSignal);
					AddDeclarativeItem(canReadToDoReadSignal);
				}

				portMap.Add(FifoElementEntity.DataOutPortName, dataSignal.Name);
				portMap.Add(FifoElementEntity.CanReadPortName, doReadToCanWriteSignal.Name);
				portMap.Add(FifoElementEntity.DoReadPortName, canReadToDoReadSignal.Name);

				portMap.Add(FifoElementEntity.ClockPortName, ClockPortName);
				portMap.Add(FifoElementEntity.ResetPortName, ResetPortName);

				AddPortMap(portMap);				
			}

			lastCanReadValueRegister =
				CreateRegister("last_can_read_value", new StdLogic());

			insertNoLongerEmptyCircuitry(doReadToCanWriteSignal);

			// finally insert the register update process
			Process registerUpdateProcess = 
				Weazel.Badger.RegisterUpdateProcess.Get(this);
			Processes.Add(registerUpdateProcess);
		}


		public static DesignFile CreateQueueDesignFile(int dataWidth, int length, FifoElementRegistry elementRegistry)
		{
			HardwareQueueEntity queue = new HardwareQueueEntity(dataWidth, length, elementRegistry);
			DesignFile file = new DesignFile(queue.Name);
			file.Entities.Add(queue);

			// add necessary libraries
			file.DesignUnit.Add("library ieee;");
			file.DesignUnit.Add("use IEEE.std_logic_1164.all;");
			file.DesignUnit.Add("use IEEE.std_logic_arith.all;");
			file.DesignUnit.Add("library work;");

			return file;
		}
	}
}
